In [ ]:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns ; sns.set()
import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots

ibm = pd.read_csv('ibm.csv')

Análise de atrição e desempenho de funcionários¶

Ter a capacidade de contratar, treinar e manter um corpo de funcionários competente e capacitado é uma das peças mais importantes dentro do esquema de uma empresa. Para estimular seus talentos a permanecerem exercendo suas atividades atuais, a empresa deve elaborar uma estratégia de retenção de funcionários.

Uma empresa que possui uma estratégia bem definida nesse aspecto cria um ambiente de trabalho mais saudável e motiva seus colaboradores a gerarem resultados. Uma regularidade maior no corpo de funcionários pode gerar alguns benefícios:

  • maior qualidade nas entregas
  • maior engajamento das equipes
  • redução de gastos com demissões
  • maior entrosamento interno
  • fortalecimento do capital intelectual

Esse é um dataset fictício criado por cientistas de dados da IBM com diversos dados de quase 1500 colaboradores. Como o objetivo geral de uma empresa deve ser manter seus talentos e fortalecer seu capital intelectual, essa análise busca entender quais fatores levam a uma maior insatisfação por parte dos colaboradores, permitindo que a empresa intervenha em determinados aspectos.

  • IBM HR Analytics Employee Attrition & Performance
  • https://www.kaggle.com/datasets/pavansubhasht/ibm-hr-analytics-attrition-dataset

Características e formato¶

Linhas e colunas

In [ ]:
ibm.shape
Out[ ]:
(1470, 35)

Nomes de coluna

In [ ]:
ibm.columns
Out[ ]:
Index(['Age', 'Attrition', 'BusinessTravel', 'DailyRate', 'Department',
       'DistanceFromHome', 'Education', 'EducationField', 'EmployeeCount',
       'EmployeeNumber', 'EnvironmentSatisfaction', 'Gender', 'HourlyRate',
       'JobInvolvement', 'JobLevel', 'JobRole', 'JobSatisfaction',
       'MaritalStatus', 'MonthlyIncome', 'MonthlyRate', 'NumCompaniesWorked',
       'Over18', 'OverTime', 'PercentSalaryHike', 'PerformanceRating',
       'RelationshipSatisfaction', 'StandardHours', 'StockOptionLevel',
       'TotalWorkingYears', 'TrainingTimesLastYear', 'WorkLifeBalance',
       'YearsAtCompany', 'YearsInCurrentRole', 'YearsSinceLastPromotion',
       'YearsWithCurrManager'],
      dtype='object')

Tipo de variável por coluna

In [ ]:
ibm.dtypes
Out[ ]:
Age                          int64
Attrition                    int64
BusinessTravel              object
DailyRate                    int64
Department                  object
DistanceFromHome             int64
Education                   object
EducationField              object
EmployeeCount                int64
EmployeeNumber               int64
EnvironmentSatisfaction     object
Gender                      object
HourlyRate                   int64
JobInvolvement              object
JobLevel                     int64
JobRole                     object
JobSatisfaction             object
MaritalStatus               object
MonthlyIncome                int64
MonthlyRate                  int64
NumCompaniesWorked           int64
Over18                       int64
OverTime                     int64
PercentSalaryHike            int64
PerformanceRating           object
RelationshipSatisfaction    object
StandardHours                int64
StockOptionLevel             int64
TotalWorkingYears            int64
TrainingTimesLastYear        int64
WorkLifeBalance             object
YearsAtCompany               int64
YearsInCurrentRole           int64
YearsSinceLastPromotion      int64
YearsWithCurrManager         int64
dtype: object

Primeiras 10 linhas

In [ ]:
ibm.head(10)
Out[ ]:
Age Attrition BusinessTravel DailyRate Department DistanceFromHome Education EducationField EmployeeCount EmployeeNumber ... RelationshipSatisfaction StandardHours StockOptionLevel TotalWorkingYears TrainingTimesLastYear WorkLifeBalance YearsAtCompany YearsInCurrentRole YearsSinceLastPromotion YearsWithCurrManager
0 41 1 Travel_Rarely 1102 Sales 1 College Life Sciences 1 1 ... Low 80 0 8 0 Bad 6 4 0 5
1 49 0 Travel_Frequently 279 Research & Development 8 Below College Life Sciences 1 2 ... Very High 80 1 10 3 Better 10 7 1 7
2 37 1 Travel_Rarely 1373 Research & Development 2 College Other 1 4 ... Medium 80 0 7 3 Better 0 0 0 0
3 33 0 Travel_Frequently 1392 Research & Development 3 Master Life Sciences 1 5 ... High 80 0 8 3 Better 8 7 3 0
4 27 0 Travel_Rarely 591 Research & Development 2 Below College Medical 1 7 ... Very High 80 1 6 3 Better 2 2 2 2
5 32 0 Travel_Frequently 1005 Research & Development 2 College Life Sciences 1 8 ... High 80 0 8 2 Good 7 7 3 6
6 59 0 Travel_Rarely 1324 Research & Development 3 Bachelor Medical 1 10 ... Low 80 3 12 3 Good 1 0 0 0
7 30 0 Travel_Rarely 1358 Research & Development 24 Below College Life Sciences 1 11 ... Medium 80 1 1 2 Better 1 0 0 0
8 38 0 Travel_Frequently 216 Research & Development 23 Bachelor Life Sciences 1 12 ... Medium 80 0 10 2 Better 9 7 1 8
9 36 0 Travel_Rarely 1299 Research & Development 27 Bachelor Medical 1 13 ... Medium 80 2 17 3 Good 7 7 7 7

10 rows × 35 columns

Resumo das variáveis numéricas (transposto para melhor leitura)

In [ ]:
ibm.describe().transpose()
Out[ ]:
count mean std min 25% 50% 75% max
Age 1470.0 36.923810 9.135373 18.0 30.00 36.0 43.00 60.0
Attrition 1470.0 0.161224 0.367863 0.0 0.00 0.0 0.00 1.0
DailyRate 1470.0 802.485714 403.509100 102.0 465.00 802.0 1157.00 1499.0
DistanceFromHome 1470.0 9.192517 8.106864 1.0 2.00 7.0 14.00 29.0
EmployeeCount 1470.0 1.000000 0.000000 1.0 1.00 1.0 1.00 1.0
EmployeeNumber 1470.0 1024.865306 602.024335 1.0 491.25 1020.5 1555.75 2068.0
HourlyRate 1470.0 65.891156 20.329428 30.0 48.00 66.0 83.75 100.0
JobLevel 1470.0 2.063946 1.106940 1.0 1.00 2.0 3.00 5.0
MonthlyIncome 1470.0 6502.931293 4707.956783 1009.0 2911.00 4919.0 8379.00 19999.0
MonthlyRate 1470.0 14313.103401 7117.786044 2094.0 8047.00 14235.5 20461.50 26999.0
NumCompaniesWorked 1470.0 2.693197 2.498009 0.0 1.00 2.0 4.00 9.0
Over18 1470.0 1.000000 0.000000 1.0 1.00 1.0 1.00 1.0
OverTime 1470.0 0.282993 0.450606 0.0 0.00 0.0 1.00 1.0
PercentSalaryHike 1470.0 15.209524 3.659938 11.0 12.00 14.0 18.00 25.0
StandardHours 1470.0 80.000000 0.000000 80.0 80.00 80.0 80.00 80.0
StockOptionLevel 1470.0 0.793878 0.852077 0.0 0.00 1.0 1.00 3.0
TotalWorkingYears 1470.0 11.279592 7.780782 0.0 6.00 10.0 15.00 40.0
TrainingTimesLastYear 1470.0 2.799320 1.289271 0.0 2.00 3.0 3.00 6.0
YearsAtCompany 1470.0 7.008163 6.126525 0.0 3.00 5.0 9.00 40.0
YearsInCurrentRole 1470.0 4.229252 3.623137 0.0 2.00 3.0 7.00 18.0
YearsSinceLastPromotion 1470.0 2.187755 3.222430 0.0 0.00 1.0 3.00 15.0
YearsWithCurrManager 1470.0 4.123129 3.568136 0.0 2.00 3.0 7.00 17.0

Checagem de valores nulos

In [ ]:
ibm.isnull().sum()
Out[ ]:
Age                         0
Attrition                   0
BusinessTravel              0
DailyRate                   0
Department                  0
DistanceFromHome            0
Education                   0
EducationField              0
EmployeeCount               0
EmployeeNumber              0
EnvironmentSatisfaction     0
Gender                      0
HourlyRate                  0
JobInvolvement              0
JobLevel                    0
JobRole                     0
JobSatisfaction             0
MaritalStatus               0
MonthlyIncome               0
MonthlyRate                 0
NumCompaniesWorked          0
Over18                      0
OverTime                    0
PercentSalaryHike           0
PerformanceRating           0
RelationshipSatisfaction    0
StandardHours               0
StockOptionLevel            0
TotalWorkingYears           0
TrainingTimesLastYear       0
WorkLifeBalance             0
YearsAtCompany              0
YearsInCurrentRole          0
YearsSinceLastPromotion     0
YearsWithCurrManager        0
dtype: int64

Contagem de valores únicos por coluna

In [ ]:
ibm.nunique()
Out[ ]:
Age                           43
Attrition                      2
BusinessTravel                 3
DailyRate                    886
Department                     3
DistanceFromHome              29
Education                      5
EducationField                 6
EmployeeCount                  1
EmployeeNumber              1470
EnvironmentSatisfaction        4
Gender                         2
HourlyRate                    71
JobInvolvement                 4
JobLevel                       5
JobRole                        9
JobSatisfaction                4
MaritalStatus                  3
MonthlyIncome               1349
MonthlyRate                 1427
NumCompaniesWorked            10
Over18                         1
OverTime                       2
PercentSalaryHike             15
PerformanceRating              2
RelationshipSatisfaction       4
StandardHours                  1
StockOptionLevel               4
TotalWorkingYears             40
TrainingTimesLastYear          7
WorkLifeBalance                4
YearsAtCompany                37
YearsInCurrentRole            19
YearsSinceLastPromotion       16
YearsWithCurrManager          18
dtype: int64

As colunas 'EmployeeCount', 'Over18' e 'StandardHours' possuem apenas um valor cada, enquanto a coluna 'Employee Number' possui 1470 valores diferentes. Por esse motivo, nenhuma dessas colunas serão úteis na análise e serão retiradas do DataFrame.

In [ ]:
ibm.drop(['EmployeeCount', 'EmployeeNumber', 'Over18', 'StandardHours'], axis="columns", inplace=True)

Histograma de visão geral¶

In [ ]:
ibm.hist(figsize=(28,20));
No description has been provided for this image

Em uma breve análise inicial, apenas olhando os histogramas gerados, é possível tecer alguns comentários:

  • A faixa etária mais presente na empresa é entre 30 e 40 anos
  • Uma porcentagem muito grande do corpo de funcionários mora perto da empresa
  • A maioria das pessoas trabalharam em no máximo 1 empresa além da IBM
  • Quase todos os colaboradores realizaram treinamentos no ano anterior à coleta dos dados
  • Seguindo uma tendência parecida com a faixa etária, a maioria das pessoas têm, no máximo, 10 anos de carreira
  • Mais de um terço dos funcionários trabalharam horas extras

Análises de atrição¶

Criação de DataFrames adicionais para análise¶

In [ ]:
# DataFrame de atrição positiva (quando a coluna 'Attrition' = 1)
ibm_y = ibm[(ibm['Attrition'] == 1)]

# DataFrame de atrição positiva (quando a coluna 'Attrition' = 0)
ibm_n = ibm[(ibm['Attrition'] == 0)]

Relação de atrição e idade¶

In [ ]:
age_att = px.box(ibm, x="Attrition", y="Age")
age_att.show(renderer='notebook')
  • Mediana de não-atrição: 36
  • Mediana de atrição: 32

Percebemos que a idade é um fator importante na atrição de um colaborador, inclusive classificando como outliers dois funcionários insatisfeitos aos 56 e 58 anos. Uma possível explicação para isso é a mudança de comportamento corporativo de uma pessoa ao longo de sua carreira: pessoas mais jovens tendem a explorar suas opções de carreira, enquanto os mais velhos buscam por maior estabilidade em um mesmo emprego.

Relação de atrição e departamento¶

In [ ]:
dept_y = ibm_y.groupby('Department')['Department'].count()
dept = ibm.groupby('Department')['Department'].count()
fig = make_subplots(rows=1, cols=2, specs=[[{'type':'domain'}, {'type':'domain'}]])
fig.add_trace(go.Pie(labels=dept_y.index, values=dept_y.values, name="Attrition"), 1, 1)
fig.add_trace(go.Pie(labels=dept.index, values=dept.values, name="Total"), 1, 2)
fig.update_traces(hole=0.4, hoverinfo="label+percent+name")
fig.update_layout(
    title_text="Department attrition analysis",
    annotations=[dict(text='Attrition = Yes', x=0.175, y=-0.2, font_size=15, showarrow=False),
                 dict(text='All employees', x=0.825, y=-0.2, font_size=15, showarrow=False)])
fig.show(renderer='notebook')

Essa comparação nos permite perceber dois departamentos em situações opostas. O departamento de P&D está no lado positivo da comparação: possui 65% do total de funcionários da empresa mas "apenas" 56% dos insatisfeitos. No lado negativo, temos Vendas, que se destaca muito mais no gráfico de atrição: nesse departamento trabalham 30% dos funcionários da empresa, mas ele representa quase 39% dos insatisfeitos.

In [ ]:
dept_att = ibm.groupby('Department')['Attrition'].value_counts()
fig = make_subplots(rows=1, cols=3, specs=[[{'type':'domain'}, {'type':'domain'}, {'type':'domain'}]])
fig.add_trace(go.Pie(labels=['No', 'Yes'], values=[dept_att[0], dept_att[1]], name="HR"), 1, 1)
fig.add_trace(go.Pie(labels=['No', 'Yes'], values=[dept_att[2], dept_att[3]], name="R&D"), 1, 2)
fig.add_trace(go.Pie(labels=['No', 'Yes'], values=[dept_att[4], dept_att[5]], name="Sales"), 1, 3)
fig.update_traces(hole=0.4, hoverinfo="label+percent+name")
fig.update_layout(
    title_text="Attrition inside departments",
    annotations=[dict(text='Human Resources', x=0.072, y=-0.2, font_size=15, showarrow=False),
                 dict(text='Research & Development', x=0.5, y=-0.2, font_size=15, showarrow=False),
                 dict(text='Sales', x=0.872, y=-0.2, font_size=15, showarrow=False)])
fig.show(renderer='notebook')

Esse gráfico analisa a relação atrito-departamento no sentido contrário, exibindo a porcentagem de atrição dentro de cada departamento. As tendências da análise anterior, no entanto, se repetem.

  • O departamento de Vendas é o destaque negativo, com 20.6% de seus funcionários se mostrando insatisfeitos
  • O departamento de P&D é o destaque positivo, com o menor índice de insatisfação entre os 3 analisados (13.8%)

Relação de atrição e distância casa-trabalho¶

In [ ]:
fig = px.box(ibm, x="Attrition", y="DistanceFromHome")
fig.show(renderer='notebook')

O box plot nos ajuda a avaliar o impacto da distância casa-trabalho na insatisfação de um funcionário:

  • Mediana de não atrição: 7
  • Mediana de atrição: 9 (+28.6%)

Também é possível perceber a grande concentração dos valores relativos aos funcionários satisfeitos. 75% deles moram a menos de 13km da empresa, enquanto seus colegas mais distantes percorrem mais que o dobro: 29km.

Relação de atrição e nível do cargo¶

In [ ]:
fig = px.histogram(ibm, x="JobLevel", color="Attrition", text_auto=True)
fig.show(renderer='notebook')

A proporção de insatisfeitos segue o nível do cargo, com o nível 1 tendo a maior taxa de atrição: 26,3%

Relação de atrição e salário¶

In [ ]:
income = px.box(ibm, x="Attrition", y="MonthlyIncome")
income.show(renderer='notebook')

O salário é claramente um dos fatores mais impactantes na existência ou não de atrição do trabalhador, com o modelo classificando como outliers os 10 funcionários insatisfeitos com salários acima de 11 mil.

  • Mediana de atrição: 3202
  • Mediana de não-atrição: 5204
  • Uma diferença extremamente notável de 62.5%!

Relação de atrição e horas extras¶

In [ ]:
ot = ibm.groupby('OverTime')['Attrition'].value_counts()
fig = make_subplots(rows=1, cols=2, specs=[[{'type':'domain'}, {'type':'domain'}]])
fig.add_trace(go.Pie(labels=['No attrition','Attrition'], values=ot[0], name="No overtime"), 1, 1)
fig.add_trace(go.Pie(labels=['No attrition','Attrition'], values=ot[1], name="Overtime"), 1, 2)
fig.update_traces(hole=0.4, hoverinfo="label+percent")
fig.update_layout(
    title_text="Analysis of attrition due overtime",
    annotations=[dict(text='No overtime', x=0.173, y=-0.2, font_size=15, showarrow=False),
                 dict(text='Overtime', x=0.815, y=-0.2, font_size=15, showarrow=False)])
fig.show(renderer='notebook')

A variável referente às horas extras provavelmente representará, dentre todas, a com maior correlação com atrição. Não é possível traçar exatamente a curva de impacto da quantidade de horas extras, uma vez que a coluna OverTime é binária. Ainda assim, a diferença de atrição se mostra notável no gráfico. A proporção de funcionários insatisfeitos quase triplica - de 10.4% para 30.5% - dos que trabalharam sua carga normal para os que realizaram horas extras.

Análise de correlação entre atrição e demais variáveis¶

In [ ]:
# Select only numerical columns
ibm_corr = ibm.select_dtypes(include=[float, int])

# Calculate the correlation matrix
corr_matrix = ibm_corr.corr()

# Continue with your code for sorting and resetting the index...
corr = corr_matrix.unstack().sort_values(ascending=False)
corr_df = pd.DataFrame(corr).reset_index()
corr_df.columns = ['Var_A', 'Var_B', 'Corr']
corr_df = corr_df.query("Var_A == 'Attrition' and Var_B != 'Attrition'")
corr_df
Out[ ]:
Var_A Var_B Corr
70 Attrition OverTime 0.246118
84 Attrition DistanceFromHome 0.077924
93 Attrition NumCompaniesWorked 0.043494
137 Attrition MonthlyRate 0.015170
213 Attrition HourlyRate -0.006846
231 Attrition PercentSalaryHike -0.013478
273 Attrition YearsSinceLastPromotion -0.033019
295 Attrition DailyRate -0.056652
296 Attrition TrainingTimesLastYear -0.059478
308 Attrition YearsAtCompany -0.134392
311 Attrition StockOptionLevel -0.137145
312 Attrition YearsWithCurrManager -0.156199
314 Attrition Age -0.159205
316 Attrition MonthlyIncome -0.159840
318 Attrition YearsInCurrentRole -0.160545
321 Attrition JobLevel -0.169105
322 Attrition TotalWorkingYears -0.171063

Conclusão¶

A base de dados fornecia quase 20 dados diferentes sobre cada funcionário e por isso foi necessário gerar algumas visualizações para compreender melhor o impacto de cada variável. Essas diferentes visualizações, entretanto, geraram algumas conclusões interessantes.

A idade foi uma variável com relações fortes com a atrição. Não é possível cravar que seja esse o motivo, mas uma possível explicação é a mudança de pensamento profissional de acordo com a idade. No início de uma carreira, as pessoas costumam estar mais dispostas a assumir riscos e explorar oportunidades, facilitando o surgimento de insatisfações no cargo atual. Com o tempo e amadurecimento, a tendência profissional é buscar maior estabilidade, permanecendo um tempo maior na instituição.

Também foi possível perceber outras relações naturais de surgimento de insatisfação, como a distância casa-trabalho. A base de dados, por ser fictícia, não retrata uma cidade ou país específico, mas essa relação é forte em cidades grandes. Trazendo a análise para o Brasil, cidades como São Paulo e Rio de Janeiro sofrem com constantes problemas de transporte público e engarrafamentos em horários de pico. Distâncias relativamente curtas, como 4 ou 5 quilômetros, podem exigir muito tempo de deslocamento. Isso pode causar um duplo efeito negativo na pessoa que realiza esse percurso: o estresse gerado pelo caos urbano e a decepção causada pelo tempo perdido. Quanto mais tempo é perdido nos percursos de ida e volta, menos tempo livre a pessoa terá.

Apesar das relações citadas serem evidentes pelos gráficos, a IBM não tem uma solução fácil. Não é possível mudar a idade de um funcionário e não é fácil trazer seu trabalhador para mais perto da sede. A análise, no entanto, gera alguns pontos que a IBM poderia agir. Um exemplo disso é a análise de atrição por departamento, onde temos departamentos em situações opostas. O departamento de Pesquisa e Desenvolvimento é o exemplo interno a ser seguido: "apenas" 13.8% dos funcionários dessa área estão insatisfeitos. Os outros dois setores possuem taxas de insatisfação cerca de 50% maiores (19% e 20.6%) e são uma oportunidade de intervenção. O banco de dados por si só não permite chegar a uma conclusão, mas nos possibilita afirmar que há necessidade de uma investigação detalhada. O que o departamento de P&D têm feito para manter seus funcionários felizes? Talvez essa resposta possa ser aplicada nos outros setores.

Outra relação que a IBM pode aliviar é a realização de horas extras. É a variável com a correlação mais forte dentre todas e o raciocínio é básico. Uma rotina de trabalho e deslocamento, por vezes, já é muito desgastante. O cenário piora ainda mais quando o período de trabalho é ampliado. A análise mostrou que a realização de horas extras pode triplicar a chance de um funcionário se mostrar insatisfeito.

A empresa, nesse caso, deveria buscar agir nas frentes em que pode. Nos exemplos citados acima, as intervenções seriam nos departamentos e nas horas extras. Investigar um caso que está dando certo e aplicar em outros cenários é sempre uma boa ideia e poderia ajudar a aumentar a satisfação dos funcionários, consequentemente motivando-os a permanecer na IBM, construindo uma equipe cada vez mais forte, capacitada e motivada a entregar o melhor em nome da empresa.